﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Engine.Sgml.Matching;
using Engine.Dtd.Schema;
using System.Diagnostics;
using Engine.Sgml.Merging;

namespace Engine.Sgml.Assembling {
    internal sealed class MatchAssembler {
        private ElementAssembler elementAssembler = new ElementAssembler();

        public static bool HasUncompletedMatch(IList<Capture> captures) {
            Guard.ArgumentNotNull(captures, "captures");
            if (captures.Count == 0)
                return false;
            Match match = null;
            int index = 0;
            while (index < captures.Count) {
                CaptureEntity entity = captures[index].Entity;
                if (entity.IsModelMatch) {
                    match = entity.Match;
                    break;
                    }
                index++;
                }
            Debug.Assert(match != null);
            if (match == null)
                return false;
            if (match.Type != AccordanceTypes.Full)
                return true;
            if (match.Model.CanRecur())
                while (++index < captures.Count) {
                    CaptureEntity entity = captures[index].Entity;
                    if (entity.IsModelMatch && entity.Match.Type == AccordanceTypes.Partial)
                        return true;
                    }
            return false;
            }

        public List<SElement> Assemble(IList<Capture> captures) {
            // Assemble matches
            List<SElement> createdElements = new List<SElement>();
            Match match = GetFirstMatch(captures);
            if (match.Model.CanRecur()) {
                Assemble(match, createdElements);
                for (int i = 0; i < captures.Count; i++) {
                    CaptureEntity entity = captures[i].Entity;
                    if (entity.IsModelMatch && entity.Match.Type == AccordanceTypes.Partial) {
                        Recover(entity.Match, createdElements);
                        }
                    }
                }
            else
                Assemble(match, createdElements);
            return createdElements;
            }

        private void Assemble(Match match, List<SElement> createdElements) {
            Debug.Assert(match != null);
            switch (match.Type) {
                case AccordanceTypes.Inconsistent:
                    Build(match, createdElements);
                    break;
                case AccordanceTypes.Partial:
                    Recover(match, createdElements);
                    break;
                case AccordanceTypes.Full:
                    break;
                default:
                    throw new NotSupportedException("Unknown the accordance type of a match object.");
                }
            Debug.Assert(MatchTestUtility.Verify(match));
            }

        public List<SElement> Build(Match match) {
            Guard.ArgumentNotNull(match, "match");
            Debug.Assert(match.Type == AccordanceTypes.Inconsistent);
            List<SElement> createdElements = new List<SElement>();
            Build(match, createdElements);
            Debug.Assert(match.Type == AccordanceTypes.Full);
            return createdElements;
            }

        private void Build(Match match, List<SElement> createdElements) {
            for (int i = 0; i < match.Captures.Count; i++) {
                Capture capture = match.Captures[i];
                if (capture.Success)
                    continue;
                CaptureEntity entity = capture.Entity;
                if (BuildRequired(ref entity))
                    Build(entity, i, match, createdElements);
                }
            }

        public List<SElement> Recover(Match match) {
            Guard.ArgumentNotNull(match, "match");
            Debug.Assert(match.Type == AccordanceTypes.Partial);
            Debug.Assert(match.Model.OrderType == DGroupOrderType.OnlyOne ? match.Captures.Count > 0 && match.Captures.All(c => !c.Entity.IsElement || c.Entity.IsIllegalElement || c.Entity.IsInclusion) : true);
            List<SElement> createdElements = new List<SElement>();
            Recover(match, createdElements);
            Debug.Assert(match.Type == AccordanceTypes.Full);
            return createdElements;
            }

        private void Recover(Match match, List<SElement> createdElements) {
            for (int i = 0; i < match.Captures.Count; i++) {
                Capture capture = match.Captures[i];
                CaptureEntity entity = capture.Entity;
                if (capture.Success) {
                    if (entity.IsModelMatch) {
                        Match innerMatch = entity.Match;
                        switch (innerMatch.Type) {
                            case AccordanceTypes.Inconsistent:
                                Build(innerMatch, createdElements);
                                match.Captures[i] = new Capture(innerMatch, true);
                                break;
                            case AccordanceTypes.Partial:
                                Recover(innerMatch, createdElements);
                                break;
                            case AccordanceTypes.Full:
                                break;
                            default:
                                throw new NotSupportedException("Unknown the accordance type of a match object.");
                            }
                        }
                    }
                else if (BuildRequired(ref entity))
                    Build(entity, i, match, createdElements);
                }
            }

        private static bool BuildRequired(ref CaptureEntity entity) {
            return !entity.IsIllegalElement && entity.Model.MustPresent();
            }

        private void Build(CaptureEntity entity, int index, Match match, List<SElement> createdElements) {
            if (entity.IsModelMatch) {
                Build(entity.Match, createdElements);
                match.Captures[index] = new Capture(entity.Match, true);
                }
            else if (elementAssembler.CanBuild(entity.Model, match.Ancestor.Declaration)) {
                SElement element = elementAssembler.Build(entity.Model, match.Ancestor, GetPosition(match, index));
                match.Captures[index] = new Capture(entity.Model, element);
                createdElements.Add(element);
                }
            }

        private static int GetPosition(Match match, int index) {
            for (int j = index - 1; j >= 0; j--) {
                if (match.Captures[j].Success)
                    return match.Captures[j].EndIndex + 1;
                }
            return match.StartIndex;
            }

        private static Match GetFirstMatch(IList<Capture> captures) {
            for (int i = 0; i < captures.Count; i++) {
                CaptureEntity entity = captures[i].Entity;
                if (entity.IsModelMatch)
                    return entity.Match;
                }
            throw new InvalidOperationException();
            }
        }
    }
